#pragma once
#include "stdafx.h"
#include "FileOutputBuffer.h"
#include "AlignmentsQ.h"
#include "ShortReadUtil.h"

// class conatins the result of a mapping
const int READ_BUFFER_SIZE = MAX_READ_LENGTH + 1;
const int SCORES_BUFFER_SIZE = MAX_READ_LENGTH + 1;

class CMappingResult
{
public:
    CMappingResult(void);
    CMappingResult(CAlignmentsQ& que, unsigned int uiReadLength);
    ~CMappingResult(void);
    CMappingResult& operator=(const CMappingResult& m);
    bool upgrade(const CMappingResult& m); // replace with a better alignment

    unsigned int uiReadLength;
    char QNAME[FILENAME_MAX];
    char RNAME[FILENAME_MAX];
    char caRef[READ_BUFFER_SIZE];
    char caRead[READ_BUFFER_SIZE];
    char rawScores[READ_BUFFER_SIZE];  // for calculate
    char QScores[SCORES_BUFFER_SIZE];  // for print
    unsigned int uiGlobalMappedPos, uiRefId, uiPOS, uiRefLength;
    unsigned int uiDiff;
    int mismatchScore; //Sum of quality score of mismatches bases, if quality score are available.
    char strand;
    unsigned int MultipleMappedNo;
    char SNPtype; // If not SOLiD read, print a space. If SOLiD read, the SNP type indicated by N, C, V, T
    // Attribute only for SAM
    inline void getReverseReadandQual(void);
    inline void setSingleEndSamFields(void);
    inline void setSingleEndSamFlags(void);
    inline void setPairedEndSamFields(void);
    inline void setPairedEndSamFlags(bool firstRead, char mateStrand);
    // For SAM format, which prints read in ref direction
    char revComRead[READ_BUFFER_SIZE];
    char revQScores[SCORES_BUFFER_SIZE];
    int  FLAG;
    int  MAPQ;
    int  ISIZE;
    unsigned int  uiMPOS;
    unsigned int uiSkippedLength;  // N in CIGAR format
    unsigned int uiSkippedPos;
    int matchScoreL;  // For spliced alignment
    int matchScoreR; // For spliced alignment
    int MEMES;
    char CIGAR[FILENAME_MAX];
    char MRNM[FILENAME_MAX];
    char TAG[MAX_LINE];
private:
    inline void initialization(void);
};

inline void CMappingResult::getReverseReadandQual(void)
{
    strcpy(revComRead, caRead);
    strcpy(revQScores, QScores);
    reverseComplementKmer(revComRead);
    reverseKmer(revQScores);
}

inline void CMappingResult::setSingleEndSamFlags(void)
{
    this->FLAG = 0;
    if (this->strand == '-') {
        this->FLAG += 0x10;
    }
}

inline void CMappingResult::setPairedEndSamFields(void)
{
    // Assume flag, uiMPOS, and ISIZE are set outside
    this->MAPQ = 255;
    this->MRNM[0] = '=';
    this->MRNM[1] = '\0';
    if (this->uiSkippedLength > 0 && this->uiReadLength > this->uiSkippedPos)  {
        sprintf(this->CIGAR, "%uM%uN%uM", \
                this->uiSkippedPos, this->uiSkippedLength, this->uiReadLength - this->uiSkippedPos);
    } else {
        sprintf(this->CIGAR, "%uM", this->uiReadLength);
    }
    sprintf(this->TAG, "NM:i:%d", this->uiDiff);
}

inline void CMappingResult::setSingleEndSamFields(void)
{
    this->setSingleEndSamFlags(); // Single strang + -, 64, 80
    this->MAPQ = 255;
    this->MRNM[0] = '*';
    this->MRNM[1] = '\0';
    this->uiMPOS = 0;
    this->ISIZE = 0;
    sprintf(this->CIGAR, "%uM", this->uiReadLength);
    if (this->TAG[0] == '\0') {
        sprintf(this->TAG, "NM:i:%d", this->uiDiff);
    } else {
        string tag(this->TAG);
        sprintf(this->TAG, "NM:i:%d\t%s", this->uiDiff, tag.c_str());
    }
}

inline int setFlag(int flags, int digit, bool value)
{
    int flag = (0x01 << digit);
    if (value) {
        return( flags | flag);
    } else {
        return( flags & (~flag));
    }
}

inline string getSamRG(const char* readsName, bool bSOLiD)
{
    char RG[MAX_LINE];
    const char* platform = bSOLiD ? "SOLiD" : "Illumina";
    sprintf(RG, "@RG\tID:%s\tSM:%s\tPU:%s ", readsName, readsName, platform);
    return(string(RG));
}

inline void setSamFlags(CMappingResult &m1, CMappingResult &m2, int m1No, int m2No, bool bInRange)
{
    // potential error if one of the read is not mapped however the strand flag is still set
    int flag = 1 + 0x02*(1-(int)bInRange);
    m1.FLAG = flag + 0x04*((int)(m1No == 0)) + 0x08*((int)(m2No == 0)) + 0x40;
    m1.FLAG += (int)(m1.strand == '-') * 0x10 + (int)(m2.strand == '-') * 0x20;
    m2.FLAG = flag + 0x04*((int)(m2No == 0)) + 0x08*((int)(m1No == 0)) + 0x80;
    m2.FLAG += (int)(m2.strand == '-') * 0x10 + (int)(m1.strand == '-') * 0x20;
}

inline void resetStrandSamFlag(CMappingResult &m)
{
    const int strandQDigit = 4;
    bool negativeStrand = (m.strand == '-');
    m.FLAG = setFlag(m.FLAG, strandQDigit, negativeStrand);
}

inline void resetStrandSamFlags(CMappingResult &m1, CMappingResult &m2)
{
    const int strandQDigit = 4;
    const int strandMDigit = 5;
    m1.FLAG = setFlag(m1.FLAG, strandQDigit, m1.strand == '-');
    m2.FLAG = setFlag(m2.FLAG, strandQDigit, m2.strand == '-');
    m1.FLAG = setFlag(m1.FLAG, strandMDigit, m2.strand == '-');
    m2.FLAG = setFlag(m2.FLAG, strandMDigit, m1.strand == '-');
}

inline void setSamFlags4OnlyOneEndMapped(CMappingResult &m, bool firstRead)
{
    int flag = 0x01;
    m.FLAG = flag + 0x08 + (int)(m.strand == '-') * 0x40;
    if (firstRead) {
        m.FLAG += 0x40;
    } else {
        m.FLAG += 0x80;
    }
}

inline void printDelMappingInSam(FileOutputBuffer* AlignResult, CMappingResult& m, bool bPrintRef)
{
    resetStrandSamFlag(m);
    // fake quality score
    if (m.QScores[0] == '\0') {
        memset(m.QScores, 'I', sizeof(char) * m.uiReadLength);
    }
    m.QScores[m.uiReadLength] = '\0';
    m.uiMPOS  = 0;
    m.MRNM[0] = '*';
    m.MRNM[1] = '\0';
    if (m.QScores[0] == '\0') {
        sprintf(m.TAG,"NM:i:%d", m.mismatchScore); //print mismatch score
    } else {
        sprintf(m.TAG,"MS:i:%d", m.mismatchScore); //print mismatch score
    }
    unsigned int uiMPOS = (m.MRNM[0] == '*' ? 0 : m.uiMPOS + 1);

    const char* SEQ = m.caRead;
    const char* QUAL = m.QScores;
    if (m.strand == '-') {
        m.getReverseReadandQual();
        SEQ = m.revComRead;
        QUAL = m.revQScores;
    }
    if (bPrintRef) {
        string tag = string(m.TAG);
        tag = tag.append("\tZR:z:").append(string(m.caRef));
        myStrCpy(m.TAG, tag.c_str(), MAX_LINE);
    }
    sprintf(AlignResult->caBufp, "%s\t%d\t%s\t%u\t%d\t%s\t%s\t%u\t%d\t%s\t%s\t%s\n",\
            m.QNAME, m.FLAG, m.RNAME, m.uiPOS + 1, m.MAPQ, m.CIGAR,\
            m.MRNM, uiMPOS, m.ISIZE, SEQ, QUAL, m.TAG);
    AlignResult->UpdateSize();
}

inline void printAlignmentInSam(FileOutputBuffer* AlignResult, CMappingResult& m)
{
    char* SEQ = (m.strand == '+') ? m.caRead : m.revComRead;
    char* QUAL = (m.strand == '+') ? m.QScores : m.revQScores;
    resetStrandSamFlag(m);
    // fake quality score
    if (QUAL[0] == '\0') {
        memset(QUAL, 'I', sizeof(char) * m.uiReadLength);
    }
    QUAL[m.uiReadLength] = '\0';
    unsigned int uiMPOS = (m.MRNM[0] == '*' ? 0 : m.uiMPOS + 1);
    sprintf(AlignResult->caBufp, "%s\t%d\t%s\t%u\t%d\t%s\t%s\t%u\t%d\t%s\t%s\t%s\n",\
            m.QNAME, m.FLAG, m.RNAME, m.uiPOS + 1, m.MAPQ, m.CIGAR,\
            m.MRNM, uiMPOS, m.ISIZE, SEQ, QUAL, m.TAG);
    AlignResult->UpdateSize();
}

// Assume both end are mapped to a same reference
inline bool printAPairedMappingInSam\
(FileOutputBuffer* AlignResult, CMappingResult &m1, CMappingResult &m2)
{
    const bool bMapped = true;
    setSamFlags(m1, m2, (int)(bMapped), (int)(bMapped), bMapped);
    m1.setPairedEndSamFields();
    m2.setPairedEndSamFields();
    m1.uiMPOS = m2.uiPOS;
    m2.uiMPOS = m1.uiPOS;
    strcpy(m2.QNAME, m1.QNAME); // sam format needs a same query name for a pair.
    printAlignmentInSam(AlignResult, m1);
    printAlignmentInSam(AlignResult, m2);
    return(true);
}

inline string getCategory(char strand1, char strand2, int ISIZE, int disLB, int disUB)
{
    char category[4];
    category[3] = '\0';
    category[0] = (strand1 == strand2) ? 'A' : 'B';
    category[1] = (strand1 == '-') ? 'B' : 'A';
    if (ISIZE > disUB) {
        category[2] = 'B';
    } else if (ISIZE < disLB) {
        category[2] = 'C';
    } else {
        category[2] = 'A';
    }
    return(string(category));
}
